package cn.hiapi.core.basic.service.impl;

import cn.hiapi.core.basic.BasicEntity;
import cn.hiapi.core.basic.ResUtils;
import cn.hiapi.core.basic.jpa.BasicRepository;
import cn.hiapi.core.basic.query.BasicQuery;
import cn.hiapi.core.basic.query.QueryWrapper;
import cn.hiapi.core.basic.response.ResponseEntity;
import cn.hiapi.core.basic.service.BasicService;
import cn.hiapi.core.basic.exception.BasicException;
import jakarta.annotation.Resource;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;

/**
 * @author AdinZ
 * @date 2021/1/6 10:03 上午
 */
public class BasicServiceImpl<Repository extends BasicRepository<T, ID>, T extends BasicEntity, ID extends Serializable>
        implements BasicService<T, ID> {

    @Autowired
    protected Repository repository;

    @Resource
    protected EntityManagerFactory factory;


    @Override
    public EntityManagerFactory getFactory() {
        return factory;
    }

    @Override
    public void closeEntityManger(EntityManager em) {
        EntityManagerFactoryUtils.closeEntityManager(em);
    }


    @Override
    public T get(ID id) {
        final Optional<T> optional = this.repository.findById(id);
        return optional.orElse(null);
    }


    @Override
    public void delete(ID... ids) {
        try {
            this.repository.deleteAllById(Arrays.asList(ids));
        } catch (EmptyResultDataAccessException e) {
            throw new BasicException(4004, "数据不存在,无法删除");
        }
    }

    @Override
    public void delete(List<T> list) {
        repository.deleteAllInBatch(list);
    }

    @Override
    public void delete(T t) {
        this.repository.delete(t);
    }

    @Override
    public T update(T t) {
        return this.repository.save(t);
    }

    @Override
    public List<T> update(List<T> list) {
        return this.repository.saveAll(list);
    }

    @Override
    public T save(T t) {
        return this.repository.save(t);
    }

    @Override
    public List<T> save(List<T> list) {
        return this.repository.saveAll(list);
    }

    @Override
    public List<T> findAll() {
        return this.repository.findAll();
    }

    @Override
    public List<T> findAll(Sort sort) {
        return this.repository.findAll(sort);
    }

    @Override
    public List<T> findList(QueryWrapper query) {
        return this.findList(query, Sort.unsorted());
    }

    @Override
    public List<T> findList(QueryWrapper query, Sort sort) {
        return this.repository.findAll(new Query<T>(query).getSpecification(), sort);
    }

    @Override
    public long count(QueryWrapper query) {
        return this.repository.count(new Query<T>(query).getSpecification());
    }

    @Override
    public List<T> findList(ID[] ids) {
        return this.repository.findAllById(Arrays.asList(ids));
    }

    @Override
    public List<T> findList(Collection<ID> list) {
        return this.repository.findAllById(list);
    }

    @Override
    public ResponseEntity<List<T>> findPage(QueryWrapper query, Pageable pageable) {
        Page<T> page = this.repository.findAll(new Query<T>(query).getSpecification(), pageable);
        ResponseEntity<List<T>> response = ResUtils.toSuccess(page.getContent());
        response.setTotal(page.getTotalElements(), page.getTotalPages());
        response.setTotalPage((long) page.getTotalPages());
        if (response.getData() == null) response.setData(new ArrayList<T>());
        return response;
    }

    @Override
    public ResponseEntity<List<T>> findPage(BasicQuery query) {
        if (query.getProperties() == null || query.getProperties().length == 0)
            query.setProperties("id");
        if (query.getDirection() == null) query.setDirection(Sort.Direction.DESC);
        return this.findPage(query.getQueryWrapper(), query.getPageable());
    }

    @Override
    public ResponseEntity<List<T>> findPage(Pageable pageable) {
        Page<T> page = this.repository.findAll(pageable);
        ResponseEntity<List<T>> response = ResUtils.toSuccess(page.getContent());
        response.setTotal(page.getTotalElements(), page.getTotalPages());
        return response;
    }

    @Override
    public ResponseEntity<List<T>> findPage(int page, int size) {
        return this.findPage(PageRequest.of(page, size));
    }




    @Getter
    public static class Query<T> extends BasicEntity {
        private Specification<T> specification;
        private Sort sort = Sort.unsorted();
        public Query(BasicQuery query) {
            this(query.getQueryWrapper());
        }
        public Query(QueryWrapper condition) {
            if (condition != null) {
                this.specification = (root, cq, cb) -> {

                    List<Predicate> list = new ArrayList<>();
                    condition.forEach((k, v) -> {
                        if (v.getValue() != null) {
                            list.add(this.build(k, v, root, cb));
                        }
                    });
                    List<Predicate> or = new ArrayList<>();
                    condition.getOr().forEach((k, v) -> {
                        if (v.getValue() != null) {
                            or.add(this.build(k, v, root, cb));
                        }
                    });

                    List<Predicate> where = new ArrayList<>();
                    if (!list.isEmpty()) {
                        where.add(cb.and(list.toArray(new Predicate[0])));
                    }
                    if (!or.isEmpty()) {
                        where.add(cb.or(or.toArray(new Predicate[0])));
                    }
                    return cq.where(
                            where.toArray(new Predicate[0])
                    ).getRestriction();
                };
            }
            if (condition != null && condition.getSort() != null) {
                this.sort = condition.getSort();
            }
        }
        private Predicate build(String k, QueryWrapper.ConditionValue cv, Root<T> root, CriteriaBuilder cb) {
            Predicate predicate;
            switch (cv.getKeyword()) {
                case LIKE:
                    predicate = cb.like(root.get(k), (String) cv.getValue());
                    break;
                case NE:
                    predicate = cb.notEqual(root.get(k), cv.getValue());
                    break;
                case LT:
                    predicate = cb.lt(root.get(k), Long.parseLong(cv.getValue() + ""));
                    break;
                case LE:
                    predicate = cb.le(root.get(k), Long.parseLong(cv.getValue() + ""));
                    break;
                case IN:
                    CriteriaBuilder.In<Object> in = cb.in(root.get(k));
                    predicate = in.value(cv.getValue());
                    break;
                case NOTIN:
                    predicate = cb.not(root.get(k).in(cv.getValue()));
                    break;
                case GT:
                    predicate = cb.gt(root.get(k), Long.parseLong(cv.getValue() + ""));
                    break;
                case GE:
                    predicate = cb.ge(root.get(k), Long.parseLong(cv.getValue() + ""));
                    break;
                case EQ:
                    predicate = cb.equal(root.get(k), cv.getValue());
                    break;
                case IS_NULL:
                    predicate = cb.isNull(root.get(k));
                    break;
                case IS_NOT_NULL:
                    predicate = cb.isNotNull(root.get(k));
                    break;
                case BETWEEN:
                    if (cv.getValue() instanceof Long[] between) {
                        predicate = cb.between(root.get(k), between[0], between[1]);
                    } else if (cv.getValue() instanceof BigDecimal[] between) {
                        predicate = cb.between(root.get(k), between[0], between[1]);
                    } else throw new BasicException("between data type err");
                    break;
                default:
                    return null;
            }
            return predicate;
        }

    }

    public ResponseEntity<List<T>> toPage(Page<T> page) {
        final ResponseEntity<List<T>> responseEntity = ResUtils.toSuccess(page.getContent());
        responseEntity.setTotal(page.getTotalElements(), page.getTotalPages());
        return responseEntity;
    }


}
